#pragma once

#include <iostream>
#include <string>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/resource.h>
#include "../comm/util.h"
#include "../comm/Log.h"
using namespace std;

// 运行功能：用于运行已经编译好的文件,只考虑能否正确运行
// 并不考虑测试用例能否运行通过
namespace ns_run
{

    class runner
    {
    public:
        runner() {}
        ~runner() {}

    public:
        static void SetRLimt(const int &cpu_limit, const int &mem_limit) // 单位: s KB
        {
            // 设置cpu时间限制
            struct rlimit cpu_rlt;
            cpu_rlt.rlim_cur = cpu_limit;
            cpu_rlt.rlim_max = RLIM_INFINITY;
            setrlimit(RLIMIT_CPU, &cpu_rlt);

            // 设置内存地址空间限制
            struct rlimit mem_rlt;
            mem_rlt.rlim_cur = mem_limit * 1024;
            mem_rlt.rlim_max = RLIM_INFINITY;
            setrlimit(RLIMIT_AS, &mem_rlt);
        }

    public:
        //************************************************************************
        // 参数：
        // file_name: 编译好的生成的可执行文件名(file_name不带后缀.exe)
        // cpu_limit: cpu时间资源限制
        // mem_limit: 地址空间资源限制
        //
        // 返回值：
        // 程序运行结果-子进程运行代码时的退出信号
        // >0: 退出异常,程序运行出错,并收到信号(返回值)
        // =0: 运行正常(结果不确定)
        // <0: 内部调用函数出错
        // 代码运行出错就会向2号fd中输出数据,但是不一定是收到了信号
        //************************************************************************
        static int run(const string &file_name, int cpu_limit, int mem_limit)
        {
            // 0.提前存好运行时的临时文件名称并打开(此时会被子进程继承下去)
            string _exe = ns_util::path_util::Exe(file_name);
            string _stdin = ns_util::path_util::Stdin(file_name);
            string _stdout = ns_util::path_util::Stdout(file_name);
            string _stderr = ns_util::path_util::Stderr(file_name);
            if (!ns_util::file_util::FileExit(_exe)) // 没有可执行文件
            {
                ns_log::LOG(WARNING) << " : 没有可执行文件" << endl;
                exit(1);
            }

            umask(0);
            int stdin_fd = open(_stdin.c_str(), O_CREAT | O_RDONLY | O_TRUNC, 0664);
            int stdout_fd = open(_stdout.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0664);
            int stderr_fd = open(_stderr.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0664);
            if (stdin_fd < 0 || stdout_fd < 0 || stderr_fd < 0)
            {
                ns_log::LOG(WARNING) << " : open调用出错" << endl;
                return -1; // 文件open错误
            }

            //  1.创建子进程
            //  2.让子进程进行程序替换执行代码运行
            //  3.运行的结果会返回到标准输出、错误
            //  *.添加新功能，用于检测运行是否超出内存(KB)和时间(s) setrlimit

            pid_t pid = fork();
            if (pid < 0)
            {
                close(stdin_fd);
                close(stdout_fd);
                close(stderr_fd);

                ns_log::LOG(ERROR) << " : fork调用出错" << endl;
                return -2;
            }
            else if (pid == 0) // 子进程
            {
                // 1.重定向
                dup2(stdin_fd, 0);
                dup2(stdout_fd, 1);
                dup2(stderr_fd, 2);

                // *.资源限制
                SetRLimt(cpu_limit, mem_limit);

                // 2.程序替换(执行运行程序的方法)
                execl(_exe.c_str(), _exe.c_str(), nullptr);

                // ***程序替换成功以后，后续代码不会执行（已经被替换掉了）
                ns_log::LOG(ERROR) << " : execlp调用出错" << endl;
                return -3; // 如果程序替换出错
            }
            else // 父进程
            {
                close(stdin_fd);
                close(stdout_fd);
                close(stderr_fd);

                int status;
                // 子进程退出信息（退出状态(9-15)+core dump(核心转储8)+中止信号(0-7)）
                waitpid(pid, &status, 0); // 阻塞式等待

                int ret = status & 0x7f;
                ns_log::LOG(INFO) << " : 运行完毕, info = " << ret << endl;

                return ret;
            }
        }
    };

};